package com.cn.lp.jpa.cq.domain;

import com.cn.lp.common.function.Func0;
import com.cn.lp.jpa.cq.CQLogicalType;
import com.cn.lp.jpa.cq.CQOrderType;
import com.cn.lp.jpa.cq.CQuery;
import com.cn.lp.jpa.cq.expression.CQCondition;
import com.cn.lp.jpa.cq.expression.CQFrom;
import com.cn.lp.jpa.cq.expression.CQLogical;
import com.cn.lp.jpa.cq.expression.CQOrderBy;
import com.google.common.collect.Lists;

import java.util.Collection;
import java.util.List;

/**
 * 语句构建者
 *
 * @param <TFrom>
 */
class CQBuildImpl<TFrom> implements CQFrom<TFrom>, CQLogical<TFrom>, CQCondition<TFrom> {

    /**
     * 数据源类型
     */
    private Class<TFrom> fromClass;
    /**
     * 条件
     */
    private CQConditionBlock<TFrom> conditionBlock = CQConditionBlock.of();
    /**
     * 顺序
     */
    private List<CQOrder<TFrom>> orders = Lists.newArrayList();

    /**
     * 最后条件类型
     */
    private CQLogicalType lastLogicalType = CQLogicalType.AND;

    /**
     * 标识
     */
    public CQSign sign;

    CQBuildImpl(Class<TFrom> fromClass) {
        this.fromClass = fromClass;
        this.sign = new CQSign(fromClass);
    }

    @Override
    public CQCondition<TFrom> where() {
        return this;
    }

    @Override
    public CQOrderBy<TFrom> orderBy(CQField field) {
        sign.markOrder(field, CQOrderType.ASC);
        orders.add(CQOrder.of(CQOrderType.ASC, field));
        return this;
    }

    @Override
    public CQOrderBy<TFrom> orderByDesc(CQField field) {
        sign.markOrder(field, CQOrderType.DESC);
        orders.add(CQOrder.of(CQOrderType.DESC, field));
        return this;
    }

    @Override
    public CQuery<TFrom> build() {
        if (!conditionBlock.isRoot()) {
            throw new IllegalCallerException("有条件区块未结束");
        }
        return CQueryImpl.of(fromClass, conditionBlock.toCondition().orElse(null), orders, sign.createSign());
    }


    @Override
    public CQLogical<TFrom> endBlock() {
        sign.markBlock(lastLogicalType, false);
        conditionBlock = conditionBlock.endBlock();
        return this;
    }

    @Override
    public CQCondition<TFrom> beginBlock() {
        sign.markBlock(lastLogicalType, true);
        conditionBlock = conditionBlock.beginBlock(lastLogicalType);
        return this;
    }

    @Override
    public <T> CQLogical<TFrom> eq(CQField field, T v) {
        sign.markCondition(lastLogicalType, "eq", field, v);
        conditionBlock.append((root, query, cb) -> cb.equal(field.toExpression(root), v), lastLogicalType);
        return this;
    }

    @Override
    public CQLogical<TFrom> eq(CQField field1, CQField field2) {
        sign.markCondition(lastLogicalType, "eq", field1, field2);
        conditionBlock.append((root, query, cb) -> cb.equal(field1.toExpression(root), field2.toExpression(root)), lastLogicalType);
        return this;
    }

    @Override
    public <T> CQLogical<TFrom> ne(CQField field, T v) {
        sign.markCondition(lastLogicalType, "ne", field, v);
        conditionBlock.append((root, query, cb) -> cb.notEqual(field.toExpression(root), v), lastLogicalType);
        return this;
    }

    @Override
    public CQLogical<TFrom> ne(CQField field1, CQField field2) {
        sign.markCondition(lastLogicalType, "ne", field1, field2);
        conditionBlock.append((root, query, cb) -> cb.notEqual(field1.toExpression(root), field2.toExpression(root)), lastLogicalType);
        return this;
    }

    @Override
    public <T extends Comparable<T>> CQLogical<TFrom> gt(CQField field, T v) {
        sign.markCondition(lastLogicalType, "gt", field, v);
        conditionBlock.append((root, query, cb) -> cb.greaterThan(field.toExpression(root), v), lastLogicalType);
        return this;
    }

    @Override
    public CQLogical<TFrom> gt(CQField field1, CQField field2) {
        sign.markCondition(lastLogicalType, "gt", field1, field2);
        conditionBlock.append((root, query, cb) -> cb.greaterThan(field1.toExpression(root), field2.toExpression(root)), lastLogicalType);
        return this;
    }

    @Override
    public <T extends Comparable<T>> CQLogical<TFrom> ge(CQField field, T v) {
        sign.markCondition(lastLogicalType, "ge", field, v);
        conditionBlock.append((root, query, cb) -> cb.greaterThanOrEqualTo(field.toExpression(root), v), lastLogicalType);
        return this;
    }

    @Override
    public CQLogical<TFrom> ge(CQField field1, CQField field2) {
        sign.markCondition(lastLogicalType, "ge", field1, field2);
        conditionBlock.append((root, query, cb) -> cb.greaterThanOrEqualTo(field1.toExpression(root), field2.toExpression(root)), lastLogicalType);
        return this;
    }

    @Override
    public <T extends Comparable<T>> CQLogical<TFrom> lt(CQField field, T v) {
        sign.markCondition(lastLogicalType, "lt", field, v);
        conditionBlock.append((root, query, cb) -> cb.lessThan(field.toExpression(root), v), lastLogicalType);
        return this;
    }

    @Override
    public CQLogical<TFrom> lt(CQField field1, CQField field2) {
        sign.markCondition(lastLogicalType, "lt", field1, field2);
        conditionBlock.append((root, query, cb) -> cb.lessThan(field1.toExpression(root), field2.toExpression(root)), lastLogicalType);
        return this;
    }

    @Override
    public <T extends Comparable<T>> CQLogical<TFrom> le(CQField field, T v) {
        sign.markCondition(lastLogicalType, "le", field, v);
        conditionBlock.append((root, query, cb) -> cb.lessThanOrEqualTo(field.toExpression(root), v), lastLogicalType);
        return this;
    }

    @Override
    public CQLogical<TFrom> le(CQField field1, CQField field2) {
        sign.markCondition(lastLogicalType, "le", field1, field2);
        conditionBlock.append((root, query, cb) -> cb.lessThanOrEqualTo(field1.toExpression(root), field2.toExpression(root)), lastLogicalType);
        return this;
    }

    @Override
    public CQLogical<TFrom> in(CQField field, Collection<?> values) {
        sign.markCondition(lastLogicalType, "int", field, values);
        conditionBlock.append((root, query, cb) -> field.toExpression(root).in(values), lastLogicalType);
        return this;
    }

    @Override
    public <T extends Comparable<T>> CQLogical<TFrom> between(CQField field, T v1, T v2) {
        sign.markBetween(lastLogicalType, field, v1, v2);
        conditionBlock.append((root, query, cb) -> cb.between(field.toExpression(root), v1, v2), lastLogicalType);
        return this;
    }

    @Override
    public CQLogical<TFrom> like(CQField field, String v) {
        sign.markCondition(lastLogicalType, "like", field, v);
        conditionBlock.append((root, query, cb) -> cb.like(field.toExpression(root), v), lastLogicalType);
        return this;
    }

    @Override
    public <T> CQLogical<TFrom> eqOpt(CQField field, T v) {
        if (v == null) {
            return this;
        }
        return eq(field, v);
    }

    @Override
    public <T> CQLogical<TFrom> neOpt(CQField field, T v) {
        if (v == null) {
            return this;
        }
        return ne(field, v);
    }

    @Override
    public <T extends Comparable<T>> CQLogical<TFrom> gtOpt(CQField field, T v) {
        if (v == null) {
            return this;
        }
        return ge(field, v);
    }

    @Override
    public <T extends Comparable<T>> CQLogical<TFrom> geOpt(CQField field, T v) {
        if (v == null) {
            return this;
        }
        return ge(field, v);
    }

    @Override
    public <T extends Comparable<T>> CQLogical<TFrom> ltOpt(CQField field, T v) {
        if (v == null) {
            return this;
        }
        return lt(field, v);
    }

    @Override
    public <T extends Comparable<T>> CQLogical<TFrom> leOpt(CQField field, T v) {
        if (v == null) {
            return this;
        }
        return le(field, v);
    }

    @Override
    public CQLogical<TFrom> inOpt(CQField field, Collection<?> values) {
        if (values == null || values.isEmpty()) {
            return this;
        }
        return in(field, values);
    }

    @Override
    public <T extends Comparable<T>> CQLogical<TFrom> betweenOpt(CQField field, T v1, T v2) {
        if (v1 == null || v2 == null) {
            return this;
        }
        return between(field, v1, v2);
    }

    @Override
    public CQLogical<TFrom> likeOpt(CQField field, String v) {
        if (v == null) {
            return this;
        }
        return like(field, v);
    }

    @Override
    public CQLogical<TFrom> likeOpt(CQField field, String prefix, String postfix, String v) {
        if (v == null) {
            return this;
        }
        return like(field, prefix.concat(v).concat(prefix));
    }

    @Override
    public <T> CQLogical<TFrom> eqIf(Func0<Boolean> judge, CQField field, T v) {
        if (!judge.call()) {
            return this;
        }
        return eq(field, v);
    }

    @Override
    public <T> CQLogical<TFrom> neIf(Func0<Boolean> judge, CQField field, T v) {
        if (!judge.call()) {
            return this;
        }
        return ne(field, v);
    }

    @Override
    public <T extends Comparable<T>> CQLogical<TFrom> gtIf(Func0<Boolean> judge, CQField field, T v) {
        if (!judge.call()) {
            return this;
        }
        return gt(field, v);
    }

    @Override
    public <T extends Comparable<T>> CQLogical<TFrom> geIf(Func0<Boolean> judge, CQField field, T v) {
        if (!judge.call()) {
            return this;
        }
        return ge(field, v);
    }

    @Override
    public <T extends Comparable<T>> CQLogical<TFrom> ltIf(Func0<Boolean> judge, CQField field, T v) {
        if (!judge.call()) {
            return this;
        }
        return lt(field, v);
    }

    @Override
    public <T extends Comparable<T>> CQLogical<TFrom> leIf(Func0<Boolean> judge, CQField field, T v) {
        if (!judge.call()) {
            return this;
        }
        return le(field, v);
    }

    @Override
    public CQLogical<TFrom> inIf(Func0<Boolean> judge, CQField field, Collection<?> values) {
        if (!judge.call()) {
            return this;
        }
        return in(field, values);
    }

    @Override
    public <T extends Comparable<T>> CQLogical<TFrom> betweenIf(Func0<Boolean> judge, CQField field, T v1, T v2) {
        if (!judge.call()) {
            return this;
        }
        return between(field, v1, v2);
    }

    @Override
    public CQLogical<TFrom> likeIf(Func0<Boolean> judge, CQField field, String v) {
        if (!judge.call()) {
            return this;
        }
        return like(field, v);
    }

    @Override
    public CQCondition<TFrom> and() {
        lastLogicalType = CQLogicalType.AND;
        return this;
    }

    @Override
    public CQCondition<TFrom> or() {
        lastLogicalType = CQLogicalType.OR;
        return this;
    }

}
